"""
Copyright 2012 Wieser Dietmar, Margit Mutschlechner.

This file is part of Multimedia Social Networking Platform.

Multimedia Social Networking Platform is free software: 
you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Multimedia Social Networking Platform is distributed
in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Multimedia Social Networking Platform.  
If not, see <http://www.gnu.org/licenses/>.

"""

from msnp.persistence import PersistenceController
from msnp.service import utils
from msnp.service import network
from msnp.service.network import sending
from msnp.model.PTPModel import BackupContent, BackupType
from msnp.model import PTPModel
from msnp.logger import Logger
from msnp.service.network import logic
from msnp.service.network.model.ReliableTransportModel import RTDeleteBackupMessage
from msnp.service.network.sending import ReliableNetworkExchange

"""This module contains all functions needed to process and distribute the backups."""

#there are two entry-points when processing backupcallbacks:
#1. updatePredecessorOfSuccessor (remotemodel) -> when the new successor searches its own backups for the crashed identity
#2. backupCallback (remotemodel) -> the real callbacks of other peers
def processBackupCallback(backups):
    """This function processes the searched backups and backups received by a callback.
    
    Args:
        backups: A list of BackupContent instances.
    """
    if backups != None:
        network.NetworkLock.acquireBackupLock()
        backupFromUsersToDelete = []
        try:
            for backup in backups:
                fromUser = backup.getFromUsername()
                user = PersistenceController.selectUserImResponsibleForUsername(fromUser)
                #if backup is not already processed
                if user == None:
                    Logger.info("process backup callback for " + fromUser)
                    backup.setFromUsername(None)
                    backup.setType(PTPModel.BackupType.UPDATE)
                    #processBackup updates my fingers because fromUsername is None
                    processBackup(backup)
                    backupFromUsersToDelete.append(fromUser)
        finally:
            network.NetworkLock.releaseBackupLock()
        #important to do this after the lock is realeased -> deadlock
        #inform network to delete specific backups by backupfromusername
        if len(backupFromUsersToDelete) > 0:
            myIdentity = network.NetworkController.getMySelf().getMyIdentity()
            message = RTDeleteBackupMessage(myIdentity, backupFromUsersToDelete)
            ReliableNetworkExchange.initiateRTMessage(message)


def processBackup(backupContent, overwriteNoneBackupFrom=False):
    """This function processes a backup in the own database.
    
    If the fromUsername is None (this peer is responsible) then the current peer has to update the fingers.
    
    Args:
        backupContent: a BackupContent instance
        overwriteNoneBackupFrom: True if the value None of the column backup_from should be overwritten, otherwise False.
        
    Returns:
        Returns True if the processing was successful, False otherwise.
    """
    
    if PersistenceController.processBackup(backupContent, overwriteNoneBackupFrom):
        if backupContent.getFromUsername() == None:
            updateBackupAtFingers(None)
        return True
    return False


def sendBackupLeave(successor):
    """This function collects the current peer's backup and sends it to the successor before.
    
    The function is called before the leave operation.
    
    Args:
        successor: the successor (PeerInfo instance)
    """
    
    myBackup = selectBackupImResponsible()
    myBackup.setType(BackupType.UPDATE)
    myBackup.setFromUsername(None)
    timestamp = sending.Sender.getTimestamp(successor)
    myBackup.setTimestamp(timestamp)
    sending.Sender.sendBackup(successor, myBackup)


###################
## SELECT BACKUP ##
###################

def selectBackupImResponsible():
    """This function collects the whole backup the current peer is responsible of from the database.
    
    The call is forwarded to the selectBackupForBackupFrom function.
    
    Returns:
        the whole backup the current peer is responsible of (BackupContent instance)
    """
    
    return selectBackupForBackupFrom(None)


def selectWholeBackup():
    """This function collects the whole backup from the database.
    
    Returns:
        the whole backup (list of BackupContent instances)
    """
    
    """Selects from the database the whole backup."""
    backups = []
    usernames = PersistenceController.selectBackupFromUsers()
    ownBackup = selectBackupImResponsible()
    backups.append(ownBackup)
    for username in usernames:
        backup = selectBackupForBackupFrom(username)
        backups.append(backup)
    return backups


def selectBackupForBackupFrom(fromUsername):
    """This function collects the whole backup for a given fromUsername from the database.
    
    Args:
        fromUsername: A String representing the user name from which the backup should be selected.
    
    Returns:
        A BackupContent object containing the backup of the given user stored in the ptp database is returned.
    """
    
    return PersistenceController.selectBackup(fromUsername)


def selectBackupFromTo(fromHashedName, toHashedName):
    """This function collects the backups with fromUsernames between the two given names from the database.
    
    Args:
        fromHashedName: the hashed from user name (long - hex)
        toHashedName: the hashed to user name (long - hex)
    
    Returns:
        A list of BackupContent instances.
    """
    
    backups = []
    users = PersistenceController.selectBackupFromUsers()
    if len(users) > 0:
        for username in users:
            userHashNameLong = long(utils.Hash.hash(username), 16)
            #select backup from predecessor to me (both exclusive)
            if logic.ResponsibilityLogic.isResponsible(fromHashedName + 1, userHashNameLong, toHashedName):
                b = PersistenceController.selectBackup(username)
                if b != None:
                    backups.append(b)
    if len(backups) == 0:
        backups = None
    return backups


def selectBackupToShiftForUsername(newSuperPeer):
    """This function collects all backups that have to be shifted from the database.
    
    Args:
        newSuperPeer: the upgrading peer (PeerInfo instance)
    
    Returns:
        a BackupContent instance, None if nothing to shift
    """
    
    #select all entries where I am responsible
    newSuperPeerName = newSuperPeer.getName()
    shiftBackupContent = None
    wholeBackupContent = selectBackupImResponsible()
    if wholeBackupContent != None:
        users = wholeBackupContent.getUsers()
        myHashedNameLong = network.NetworkController.getMySelf().getMyIdentity().getHashedNameLong()
        shiftUsers = []
        if users != None:
            newSuperPeerHashedNameLong = long(utils.Hash.hash(newSuperPeerName), 16)
            for user in users:
                hashedNameLong = long(utils.Hash.hash(user.getUsername()), 16)
                isResponsible = logic.ResponsibilityLogic.isResponsible(myHashedNameLong, hashedNameLong, newSuperPeerHashedNameLong)
                if isResponsible and myHashedNameLong != hashedNameLong:
                    shiftUsers.append(user)
            if len(shiftUsers) > 0:
                shiftUserGroups = wholeBackupContent.getUserGroupsForUsers(shiftUsers)
                shiftGroups = wholeBackupContent.getGroupsForUserGroups(shiftUserGroups)
                shiftBackupContent = BackupContent(None, shiftUsers, shiftUserGroups, shiftGroups, BackupType.UPDATE)
                timestamp = sending.Sender.getTimestamp(newSuperPeer)
                shiftBackupContent.setTimestamp(timestamp)
            else:
                Logger.info("no responsible users found for new peer " + newSuperPeerName)
        else:
            Logger.info("no users to shift for new peer " + newSuperPeerName)
    else:
        Logger.info("no backup to shift for new peer " + newSuperPeerName)
    return shiftBackupContent


####################
## UPDATE FINGERS ##
####################

def updateBackupAtFingersWithoutPredecessor(backupContent):
    """This function updates the backup at all fingers except the predecessor.
    
    Args:
        backupContent: the BackupContent instance
    """
    connectedIdentities = network.NetworkController.getMySelf().getAllConnectedIdentitiesWithoutPredecessor()
    updateBackupAtFingers(backupContent, connectedIdentities)


def updateBackupAtFingers(backupContent, connectedIdentities=None):
    """This function updates the given backup at the given identities.
    
    If the connectedIdentities parameter is None, the function collects all current connected identites.
    If the given backup is None the whole backup this peer is responsible of is updated.
    
    Args:
        backupContent: the BackupContent instance
        connectedIdentities: a list of PeerInfo instances
    """
    
    myName = network.NetworkController.getMySelf().getMyIdentity().getName()
    if connectedIdentities == None:
        connectedIdentities = network.NetworkController.getMySelf().getAllConnectedIdentities()
    for peer in connectedIdentities:
        #if backupcontent is none it means that the whole own backup has to be sent as update
        if backupContent == None:
            #get timestamp so that receiver can decide whether its a up-to-date backup
            #only needed if it is an update of the whole backup
            backupContent = selectBackupImResponsible()
            backupContent.setType(PTPModel.BackupType.UPDATE)
        #if no timestamp is set we have to add it
        if backupContent.getTimestamp() == None:
            timestamp = sending.Sender.getTimestamp(peer)
            backupContent.setTimestamp(timestamp)
        backupContent.setFromUsername(myName)
        if backupContent.getType() == PTPModel.BackupType.DELETE_SELECTED_BACKUPFROM:
            Logger.info("UPDATE peer " + peer.getName() + ": delete selected backup from peer " + backupContent.getFromUsername())
        if peer != None:
            Logger.info("UPDATE peer " + peer.getName())
            sending.Sender.sendBackup(peer, backupContent)
            
            
def insertAtFinger(newPeer):
    """This function inserts the backup the current peer is responsible of at a given peer.
    
    Args:
        newPeer: a PeerInfo instance
    """
    myName = network.NetworkController.getMySelf().getMyIdentity().getName()
    if newPeer != None and newPeer.getName() != myName:
        timestamp = sending.Sender.getTimestamp(newPeer)
        backupContent = selectBackupImResponsible()
        backupContent.setFromUsername(myName)
        backupContent.setTimestamp(timestamp)
        backupContent.setType(PTPModel.BackupType.UPDATE)
        #SENDBACKUP: backup I am responsible, type update, fromUsername=myName
        return sending.Sender.sendBackup(newPeer, backupContent)


def deleteFromFinger(oldPeer):
    """This function removes the backup this peer is responsible of from the given peer
    if it is no more contained in the list of connected identities.
    
    Args:
        oldPeer: a PeerInfo instance
    """
    
    myName = network.NetworkController.getMySelf().getMyIdentity().getName()
    backupContent = BackupContent(myName, None, None, None, PTPModel.BackupType.DELETE_ALL)
    if oldPeer != None:
        delete = True
        for peer in network.NetworkController.getMySelf().getAllConnectedIdentities():
            if peer.getName() == oldPeer.getName():
                delete = False
        if delete:
            sending.Sender.sendBackup(oldPeer, backupContent)


###################
## UPDATE BACKUP ##
###################

def setBackupFromToNone(predecessorName):
    """This function sets the backup with the given fromUsername to its own backup (backupFrom = None) and processes it.
    
    Args:
        predecessorName: the name of the predecessor (string)
    """
    
    backupContent = PersistenceController.selectBackup(predecessorName)
    backupContent.setTimestamp(getBackupTimeStamp())
    backupContent.setFromUsername(None)
    backupContent.setType(BackupType.UPDATE)
    processBackup(backupContent, True)


def deleteBackupForFromName(fromName):
    """This function deletes the backup with the given fromUsername.
    
    Args:
        fromName: the from user name (string)
    """
    
    backup = BackupContent(None, None, None, None, BackupType.DELETE_ALL)
    backup.setFromUsername(fromName)
    Logger.info("delete entries for backupname = " + str(fromName))
    processBackup(backup)
    
    
def getBackupTimeStamp():
    """This function returns the current timestamp as float.
    
    Returnst:
        the current timestamp as float.
    """
    
    timestamp = utils.DateFormatter.formatTimeAsFloat()
    return timestamp
    
